package com.bonc.sign.utils;


import cn.hutool.core.util.HexUtil;
import cn.hutool.crypto.BCUtil;
import cn.hutool.crypto.ECKeyUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.SM2;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.crypto.signers.SM2Signer;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.Strings;
import org.bouncycastle.util.encoders.Hex;

import java.io.ByteArrayInputStream;
import java.math.BigInteger;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.Security;
import java.security.Signature;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

/**
 * SM2非对称加密工具类
 */
public class SM2Utils {
    public static void main(String[] args) throws Exception {
        Map<String, String> map = generatorKey();
        SM2 sm2 = SmUtil.sm2();
        //生成后端公钥和私钥
        String publicKey1 = sm2.getPublicKeyBase64();
        String privateKey1 = sm2.getPrivateKeyBase64();
        String publicKey = map.get("publicKey");
        String privateKey = map.get("privateKey");
      //  String privateKey = sm2.getPrivateKeyBase64();
        //生成前端公钥和私钥
      //  String web_publicKey = HexUtil.encodeHexStr(((BCECPublicKey) sm2.getPublicKey()).getQ().getEncoded(false)).toUpperCase();
      //  String web_privateKey = HexUtil.encodeHexStr(BCUtil.encodeECPrivateKey(sm2.getPrivateKey())).toUpperCase();

       // System.out.println("加密后：" + SM2Utils.encrypt(publicKey, "测试"));
      //  System.out.println("加密内容：" + SM2Utils.decrypt(privateKey, SM2Utils.encrypt(publicKey, "测试")));

        //签名
        //byte[] signByte = sm2.sign("测试".getBytes());
        //验签
        //System.out.println(sm2.verify("测试".getBytes(), signByte));

        //签名 publicKey
        String sign = SM2Utils.sign(privateKey, "测试");
        //验签
        System.out.println(SM2Utils.verify("测试", publicKey, sign));

        //证书验签
     //   System.out.println(SM2Util.certVerify("XXXX", "测试", "xxx"));

    }

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    /**
     * 生成秘钥对
     * @return 公钥和私钥
     */
    public static Map<String, String> generatorKey() {
        SM2 sm2 = SmUtil.sm2();
        String publicKey = HexUtil.encodeHexStr(((BCECPublicKey) sm2.getPublicKey()).getQ().getEncoded(false)).toUpperCase();
        String privateKey = HexUtil.encodeHexStr(BCUtil.encodeECPrivateKey(sm2.getPrivateKey())).toUpperCase();
        Map<String, String> map = new HashMap<>();
        map.put("publicKey", publicKey);
        map.put("privateKey", privateKey);
        return map;
    }

    /**
     * 加密
     *
     * @param publicKey 公钥
     * @param data 明文
     * @return 密文
     */
    public static String encrypt(String publicKey, String data) {
        return SmUtil.sm2(null, publicKey)
                // 不写默认就是C1C3C2
                .setMode(SM2Engine.Mode.C1C3C2)
                .encryptHex(data.getBytes(), KeyType.PublicKey)
                // 为保障前后端逻辑一致，去掉密文前面04
                .substring(2);
    }

    /**
     * 解密
     *
     * @param privateKey 私钥
     * @param data 密文
     * @return 明文
     */
    public static String decrypt(String privateKey, String data) {
        // 为保障前后端逻辑一致，统一加上04
        data = "04" + data;
        return SmUtil.sm2(privateKey, null)
                // 不写默认就是C1C3C2
                .setMode(SM2Engine.Mode.C1C3C2)
                .decryptStr(data, KeyType.PrivateKey);
    }

    //签名
    public static String sign(String privateKey, String content) throws CryptoException, NoSuchAlgorithmException {
        ECPrivateKeyParameters privateKeyParams = ECKeyUtil.decodePrivateKeyParams(SecureUtil.decode(privateKey));
        //待签名内容转为字节数组
        byte[] message = content.getBytes();
        //获取一条SM2曲线参数
        X9ECParameters sm2ECParameters = GMNamedCurves.getByName("sm2p256v1");
        //构造domain参数
        ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(),
                sm2ECParameters.getG(), sm2ECParameters.getN());

        BigInteger privateKeyD = new BigInteger(privateKeyParams.getD().toString(16), 16);
        ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(privateKeyD, domainParameters);

        //创建签名实例
        SM2Signer sm2Signer = new SM2Signer();
        //ID默认值:1234567812345678
        sm2Signer.init(true, new ParametersWithID(new ParametersWithRandom(privateKeyParameters, SecureRandom.getInstance("SHA1PRNG")), Strings.toByteArray("1234567812345678")));
        sm2Signer.update(message, 0, message.length);
        byte[] signBytes = sm2Signer.generateSignature();
        String sign = Hex.toHexString(signBytes);
        return sign;
    }


    //验签
    public static boolean verify(String content, String publicKey, String sign)  {
        ECPublicKeyParameters publicKeyKeyParams = ECKeyUtil.decodePublicKeyParams(SecureUtil.decode(publicKey));
        //待签名内容
        byte[] message = content.getBytes();
        byte[] signData = Hex.decode(sign);
        // 获取一条SM2曲线参数
        X9ECParameters sm2ECParameters = GMNamedCurves.getByName("sm2p256v1");
        // 构造domain参数
        ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(),
                sm2ECParameters.getG(),
                sm2ECParameters.getN());
        //提取公钥点
        ECPoint pukPoint = sm2ECParameters.getCurve().decodePoint(Hex.decode(new String(Hex.encode(publicKeyKeyParams.getQ().getEncoded(true)))));
        // 公钥前面的02或者03表示是压缩公钥，04表示未压缩公钥, 04的时候，可以去掉前面的04
        ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(pukPoint, domainParameters);
        //创建签名实例
        SM2Signer sm2Signer = new SM2Signer();
        //ID默认值:1234567812345678
        ParametersWithID parametersWithID = new ParametersWithID(publicKeyParameters, Strings.toByteArray("1234567812345678"));
        sm2Signer.init(false, parametersWithID);
        sm2Signer.update(message, 0, message.length);
        //验证签名结果
        boolean verify = sm2Signer.verifySignature(signData);
        return verify;
    }

    /**
     * 证书验签
     *
     * @param certStr      证书串
     * @param plaintext    签名原文
     * @param signValueStr 签名产生签名值
     * @return
     */
    public static boolean certVerify(String certStr, String plaintext, String signValueStr) throws Exception {
        byte[] signValue = Base64.getDecoder().decode(signValueStr);
        /*
         * 解析证书
         */
        CertificateFactory CF = CertificateFactory.getInstance("X.509", "BC");
        ByteArrayInputStream byteArrayIn = new ByteArrayInputStream(Base64.getDecoder().decode(certStr));
        Certificate certificate = CF.generateCertificate(byteArrayIn);
        byteArrayIn.close();
        // 验证签名
        Signature signature = Signature.getInstance("SM3withSM2");
        signature.initVerify(certificate.getPublicKey());
        signature.update(plaintext.getBytes());
        return signature.verify(signValue);
    }
}
